﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace VirtualTournaments.Business.Tournaments
{
	public class Fixture : IEnumerable<Journey>
	{
		private static Random RANDOM_NUMBERS_GENERATOR = new Random();
		private Tournament _tournament;
		private List<Journey> _journeys;

		public Fixture(Tournament tournament)
		{
			Tournament = tournament;
			Journeys = new List<Journey>();
		}

		public virtual Tournament Tournament
		{
			get { return _tournament; }
			set { _tournament = value; }
		}

		protected virtual List<Journey> Journeys
		{
			get { return _journeys; }
			set { _journeys = value; }
		}

		public Journey GetJourney(int JourneyNumber)
		{
			return Journeys[JourneyNumber - 1];
		}

		public int JourneysCount()
		{
			return Journeys.Count;
		}

		public IEnumerator<Journey> GetEnumerator()
		{
			return Journeys.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return Journeys.GetEnumerator();
		}

		public void GenerateJourney(IEnumerable<Team> teams, MatchFactory factory)
		{
			Journey newJourney = new Journey();
			List<Team> notAssignedTeams = teams.ToList();
			int teamsCount = notAssignedTeams.Count;
			while (notAssignedTeams.Count > 1)
			{
				int teamIndex = RANDOM_NUMBERS_GENERATOR.Next(0, teamsCount--);
				Team local = notAssignedTeams[teamIndex];
				notAssignedTeams.RemoveAt(teamIndex);
				teamIndex = RANDOM_NUMBERS_GENERATOR.Next(0, teamsCount--);
				Team visitor = notAssignedTeams[teamIndex];
				notAssignedTeams.RemoveAt(teamIndex);
				newJourney.AddMatch(factory.CreateMatch(this.Tournament, local, visitor));
			}
			Journeys.Add(newJourney);
		}

		public void GenerateAllAgainstAll(IEnumerable<Team> teams, MatchFactory factory)
		{
			int teamsNumber = teams.Count();
			if (teamsNumber > 1)
			{
				List<Team> teamsCopy = teams.ToList();
				if (teamsNumber % 2 == 0)
					GenerateAllAgainstAllWithEvenNumberTeams(teamsCopy, factory, teamsNumber);
				else
					GenerateAllAgainstAllWithOddNumberTeams(teamsCopy, factory, teamsNumber);
			}
		}

		private void GenerateAllAgainstAllWithEvenNumberTeams(List<Team> Teams, MatchFactory Factory, int TeamsNumber)
		{
			int JourneysNumber = TeamsNumber - 1;
			int FirstTeamIndex = RANDOM_NUMBERS_GENERATOR.Next(TeamsNumber);
			Team FirstTeam = Teams[FirstTeamIndex];
			Teams.RemoveAt(FirstTeamIndex);
			if (TeamsNumber > 2)
			{
				int ShortestQueueLength = (TeamsNumber - 2) / 2;
				Queue<Team> Queue1 = new Queue<Team>();
				Queue<Team> Queue2 = new Queue<Team>();
				FillQueues(Queue1, Queue2, Teams, TeamsNumber - 1, ShortestQueueLength);
				for (int I = 1; I <= JourneysNumber; ++I)
				{
					Journey NewJourney = new Journey();
					if (I % 2 == 1)
					{
						NewJourney.AddMatch(Factory.CreateMatch(this.Tournament, FirstTeam, Queue2.ElementAt(ShortestQueueLength)));
						CompleteJourney(NewJourney, Queue1, Queue2, ShortestQueueLength, Factory);
					}
					else
					{
						NewJourney.AddMatch(Factory.CreateMatch(this.Tournament, Queue2.ElementAt(ShortestQueueLength), FirstTeam));
						CompleteJourney(NewJourney, Queue2, Queue1, ShortestQueueLength, Factory);
					}
					Journeys.Add(NewJourney);
				}
			}
			else
			{
				Journey NewJourney = new Journey();
				NewJourney.AddMatch(Factory.CreateMatch(this.Tournament, FirstTeam, Teams[0]));
				Journeys.Add(NewJourney);
			}
			int journeyNumber = 0;
			Journeys.ForEach(journey => journey.Number = ++journeyNumber);
		}

		private void GenerateAllAgainstAllWithOddNumberTeams(List<Team> Teams, MatchFactory Factory, int TeamsNumber)
		{
			int JourneysNumber = TeamsNumber;
			int ShortestQueueLength = (TeamsNumber - 1) / 2;
			Queue<Team> Queue1 = new Queue<Team>();
			Queue<Team> Queue2 = new Queue<Team>();
			FillQueues(Queue1, Queue2, Teams, TeamsNumber, ShortestQueueLength);
			for (int I = 1; I <= JourneysNumber; ++I)
			{
				Journey NewJourney = new Journey();
				if (I % 2 == 1)
					CompleteJourney(NewJourney, Queue1, Queue2, ShortestQueueLength, Factory);
				else
					CompleteJourney(NewJourney, Queue2, Queue1, ShortestQueueLength, Factory);
				Journeys.Add(NewJourney);
			}
			int journeyNumber = 0;
			Journeys.ForEach(journey => journey.Number = ++journeyNumber);
		}

		private void FillQueues(Queue<Team> Queue1, Queue<Team> Queue2, List<Team> Teams, int TeamsNumber, int ShortestQueueLength)
		{
			for (int I = 1; I <= ShortestQueueLength; ++I, --TeamsNumber)
			{
				int TeamIndex = RANDOM_NUMBERS_GENERATOR.Next(TeamsNumber);
				Queue1.Enqueue(Teams[TeamIndex]);
				Teams.RemoveAt(TeamIndex);
			}
			for (; TeamsNumber > 1; --TeamsNumber)
			{
				int TeamIndex = RANDOM_NUMBERS_GENERATOR.Next(TeamsNumber);
				Queue2.Enqueue(Teams[TeamIndex]);
				Teams.RemoveAt(TeamIndex);
			}
			Queue2.Enqueue(Teams[0]);
		}

		private void CompleteJourney(Journey NewJourney, Queue<Team> QueueA, Queue<Team> QueueB, int QueuesLength, MatchFactory Factory)
		{
			for (int I = 0; I < QueuesLength; ++I)
				NewJourney.AddMatch(Factory.CreateMatch(this.Tournament, QueueA.ElementAt(I), QueueB.ElementAt(QueuesLength - 1 - I)));
			Team SwappedTeam = QueueA.Dequeue();
			QueueB.Enqueue(SwappedTeam);
			SwappedTeam = QueueB.Dequeue();
			QueueA.Enqueue(SwappedTeam);
		}
	}
}
